refactor(parameters): replace template-based index mappings - #2730
Conversation
|
/label status/waiting-for-review |
Merge Protections🟢 All 2 merge protections satisfied — ready to merge. Show 2 satisfied protections🟢 Require kind label
🟢 Require version label
|
There was a problem hiding this comment.
Pull request overview
This PR refactors the index-parameter mapping layer to replace string-template JSON defaults and mapping tables with structured default builders plus explicit flat-key translation, while also centralizing RaBitQ split handling and adding a compatibility-report API to surface all JSON differences.
Changes:
- Replaces template-based default parameter JSON generation with structured builders and explicit per-key mapping for multiple index entry points (HGraph, Pyramid, IVF, BruteForce/WARP, SIMQ).
- Introduces
CompatibilityReport/CollectCompatibilityIssues()to collect all JSON differences while preserving the existing boolean compatibility check. - Centralizes RaBitQ split parsing/application and removes downstream “split-version” mutation; adds boundary validation for flat external keys (e.g., SINDI, SIMQ).
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/quantization/fp32_quantizer_parameter_test.cpp | Adds regression test ensuring compatibility reporting collects all JSON differences. |
| src/parameter.h | Adds CompatibilityReport/CompatibilityIssue and implements JSON-diff collection on Parameter. |
| src/datacell/flatten_datacell_parameter.cpp | Removes RaBitQ split-version mutation; enforces canonical split-quantizer requirement when codes_type=rabitq_split. |
| src/algorithm/sindi/sindi.cpp | Adds flat-key boundary validation (unknown-field rejection) for SINDI external params. |
| src/algorithm/simq/simq.cpp | Replaces template mapping with structured defaults + explicit key validation/mapping for SIMQ. |
| src/algorithm/pyramid/pyramid.cpp | Replaces template mapping with structured defaults + explicit flat-key translation; applies centralized RaBitQ split config. |
| src/algorithm/ivf/ivf.cpp | Replaces template mapping with structured defaults + explicit flat-key translation for IVF. |
| src/algorithm/inner_index_parameter.h | Introduces RaBitQSplitConfig API and replaces mutation-based helper with parse/apply split config functions. |
| src/algorithm/inner_index_parameter.cpp | Implements RaBitQ split parsing/validation and application into inner JSON. |
| src/algorithm/inner_index_parameter_test.cpp | Adds regression coverage for split configuration parse/apply behavior. |
| src/algorithm/hgraph/hgraph_param_mapping.cpp | Replaces template mapping with structured defaults + explicit mapping; applies centralized RaBitQ split config. |
| src/algorithm/bruteforce/bruteforce.cpp | Replaces template mapping with structured defaults + explicit flat-key translation for BruteForce/WARP. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
c633b88 to
2caebe4
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Suppressed comments (5)
src/algorithm/sindi/sindi.cpp:266
- This uses
std::unordered_setbut the file’s includes (in the shown hunk) don’t include<unordered_set>. Relying on transitive includes is fragile and may fail to compile on some toolchains; add an explicit#include <unordered_set>.
static const std::unordered_set<std::string> supported_keys = {
SPARSE_TERM_ID_LIMIT,
SPARSE_DOC_PRUNE_RATIO,
USE_REORDER_KEY,
USE_QUANTIZATION,
SPARSE_WINDOW_SIZE,
SPARSE_AVG_DOC_TERM_LENGTH,
SPARSE_DESERIALIZE_WITHOUT_FOOTER,
SPARSE_DESERIALIZE_WITHOUT_BUFFER,
SPARSE_REMAP_TERM_IDS,
SPARSE_RERANK_TYPE,
SPARSE_DMQ_SHARED_CODEBOOK_THRESHOLD,
SPARSE_IMMUTABLE,
};
src/algorithm/simq/simq.cpp:1105
- This introduces
std::unordered_setusage without an explicit<unordered_set>include in the visible include list. Add#include <unordered_set>to avoid build breaks due to missing transitive includes.
static const std::unordered_set<std::string> keys = {BRUTE_FORCE_BASE_IO_TYPE,
BRUTE_FORCE_BASE_FILE_PATH,
"init_cluster_ratio",
"max_cluster_size",
"split_start_idx",
"random_seed",
"coarse_k",
"rerank_k"};
src/parameter.cpp:45
- The collected issue messages don’t indicate which side is missing/unexpected (e.g., missing from
othervs missing fromthis). Since these messages are surfaced as diagnostics (not just internal errors), making them directional (e.g., "missing in right-hand config" / "unexpected in right-hand config") would make compatibility reports more actionable.
report.issues.push_back({child_path, "field is missing"});
src/parameter.cpp:53
- The collected issue messages don’t indicate which side is missing/unexpected (e.g., missing from
othervs missing fromthis). Since these messages are surfaced as diagnostics (not just internal errors), making them directional (e.g., "missing in right-hand config" / "unexpected in right-hand config") would make compatibility reports more actionable.
report.issues.push_back({path + "." + key, "unexpected field"});
src/quantization/fp32_quantizer_parameter_test.cpp:63
- This test assumes a specific ordering of
report.issues. If JSON object iteration order changes (e.g., due to differentnlohmann::jsonobject type or wrapper behavior), this can become flaky. Consider asserting on an order-independent representation (e.g., collectpaths into a set/vector and sort before comparison) so the test validates content rather than iteration order.
REQUIRE(report.issues.size() == 3);
REQUIRE(report.issues[0].path == "$.first");
REQUIRE(report.issues[1].path == "$.nested.second");
REQUIRE(report.issues[2].path == "$.extra");
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
src/algorithm/pyramid/pyramid.cpp:1204
- This parses a JSON string at runtime to create an empty array for defaults. Prefer constructing an empty array
JsonTypedirectly (or using an existing helper) to avoid unnecessary parsing overhead and potential parse-failure paths in a default builder.
json[NO_BUILD_LEVELS].SetJson(JsonType::Parse("[]"));
src/parameter.h:17
CompatibilityIssueintroducesstd::stringin this header; consider explicitly including<string>here to avoid relying on transitive includes (include-what-you-use).
#include <vector>
src/parameter.h:31
CompatibilityIssueintroducesstd::stringin this header; consider explicitly including<string>here to avoid relying on transitive includes (include-what-you-use).
struct CompatibilityIssue {
std::string path;
std::string message;
};
2caebe4 to
21ac9bb
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
src/algorithm/bruteforce/bruteforce.cpp:1253
- In BruteForce external param mapping,
STORE_RAW_VECTORis currently written to a top-levelquantization_params.hold_moldsfield, but the BruteForce schema placesquantization_paramsunderbase_codes(andprecise_codes). As a result, the user-providedstore_raw_vectorvalue is ignored byCreateFlattenParam(base_codes_json)/ the quantizer parameter parsing.
} else if (key == STORE_RAW_VECTOR) {
inner_json[QUANTIZATION_PARAMS_KEY][HOLD_MOLDS].SetJson(field);
} else if (key == USE_ATTRIBUTE_FILTER) {
21ac9bb to
5ea729e
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.
Suppressed comments (7)
src/algorithm/simq/simq.cpp:1096
- ValidateSIMQExternalKeys() is a file-local helper but currently has external linkage, which makes it an exported symbol from the shared library (vsag uses default visibility). Mark it static or move it into an anonymous namespace to avoid unintentionally expanding the ABI surface.
void
src/algorithm/bruteforce/bruteforce.cpp:1252
- STORE_RAW_VECTOR is currently mapped to a top-level "quantization_params.hold_molds" field, but BruteForceParameter only consumes BASE_CODES_KEY (via CreateFlattenParam(base_codes_json)). This means store_raw_vector will not affect the actual base_codes quantizer config.
} else if (key == STORE_RAW_VECTOR) {
inner_json[QUANTIZATION_PARAMS_KEY][HOLD_MOLDS].SetJson(field);
src/algorithm/pyramid/pyramid.cpp:1148
- BuildDefaultPyramidParam() is a file-local helper but currently has external linkage, which makes it an exported symbol from the shared library (vsag uses default visibility). Mark it static or move it into an anonymous namespace to avoid unintentionally expanding the ABI surface.
JsonType
src/algorithm/ivf/ivf.cpp:79
- BuildDefaultIVFParam() is a file-local helper but currently has external linkage, which makes it an exported symbol from the shared library (vsag uses default visibility). Mark it static or move it into an anonymous namespace to avoid unintentionally expanding the ABI surface.
JsonType
src/algorithm/simq/simq.cpp:1086
- BuildDefaultSIMQParam() is a file-local helper but currently has external linkage, which makes it an exported symbol from the shared library (vsag uses default visibility). Mark it static or move it into an anonymous namespace to avoid unintentionally expanding the ABI surface.
This issue also appears on line 1096 of the same file.
JsonType
src/algorithm/bruteforce/bruteforce.cpp:1143
- BuildDefaultBruteForceParam() is a file-local helper but currently has external linkage, which makes it an exported symbol from the shared library (vsag uses default visibility). Mark it static or move it into an anonymous namespace to avoid unintentionally expanding the ABI surface.
This issue also appears on line 1251 of the same file.
JsonType
src/algorithm/sindi/sindi.cpp:25
- This file uses std::unordered_set but does not include <unordered_set>. Relying on transitive includes is non-portable and can break builds across standard libraries/compilers.
#include <shared_mutex>
#include <unordered_map>
#include <vector>
9b1755d to
a11cf1c
Compare
LHT129
left a comment
There was a problem hiding this comment.
Thoroughly reviewed the full diff (32 files, ~1200 additions, ~1500 deletions). The refactoring is well-executed and addresses all major concerns from earlier review rounds:
Verified fixes from previous Copilot comments:
- STORE_RAW_VECTOR: Both base and precise codes now correctly use
ApplyHoldMoldsToQuantizer()for hold_molds propagation size_t→uint64_t: All new code usesuint64_tconsistentlySplitStringtrimming: Confirmed working, normalizes TQ chain whitespace- Test ordering: Fixed with
std::map
Architecture improvements:
- Template-based mapping replaced with structural
CreateDefault()factories → no more string template mutation chains RaBitQSplitConfigstruct +ParseRaBitQSplitConfig/ApplyRaBitQSplitConfigcentralizes split configurationCompatibilityReport/CollectCompatibilityIssues()provides structured diff reporting alongside existingCheckCompatibility- All mapping functions reject unknown flat keys with "invalid config param" errors
- SINDI/SINDIV2 now validate unknown flat keys at boundary
One remaining minor issue (already flagged by Copilot, not re-flagging):
inner_index_parameter.cpp:105: error message says"mrle, rabitq"butSplitStringnormalizes whitespace so the comparison is against"mrle,rabitq". The error message could mislead users.
No new substantive issues found. The PR is in good shape.
LHT129
left a comment
There was a problem hiding this comment.
Code Review Summary
Commit reviewed: a11cf1c
All issues flagged in the previous Copilot review have been addressed:
-
STORE_RAW_VECTOR —
ApplyHoldMoldsToQuantizeris now correctly applied to bothbase_codesandprecise_codesquantizers inbruteforce.cppandhgraph_param_mapping.cpp. -
size_t → uint64_t — Changed in
parameter.hto match VSAG coding standards. -
Test ordering dependency — Fixed by using
std::mapfor order-independent assertions infp32_quantizer_parameter_test.cpp. -
TQ TYPE_KEY — Now properly set in
TransformQuantizerParameter::ToJson()attransform_quantizer_parameter.cpp:112. -
TQ whitespace —
SplitStringtrims whitespace; verified by test.
Additional observations (no action needed)
- JSON pointer escaping (
escape_json_pointer_token/append_json_pointer_token) is RFC 6901 compliant. The test correctly verifies"/a~1b"for key"a/b". RaBitQSplitConfigstruct withParseRaBitQSplitConfig(validates external params) +ApplyRaBitQSplitConfig(mutates inner JSON) is a clean separation of concerns. The shared key"base_quantization_type"works correctly for both HGraph and Pyramid sinceHGRAPH_BASE_QUANTIZATION_TYPEandPYRAMID_BASE_QUANTIZATION_TYPEhave the same string value.build_default_flatten_paramdouble-application ofhold_molds(viaCreateDefault+ApplyHoldMoldsToQuantizer) is intentional:CreateDefaulthandles FP32/INT8 directly, whileApplyHoldMoldsToQuantizerunwraps TQ chains to reach the bottom quantizer.- SINDI/SINDIV2 now validate external params against a
supported_keysset, rejecting unknown keys with a clear error message. FlattenDataCellParameter::CreateDefaultcorrectly setshold_moldson FP32/INT8 quantizer parameter objects, andToJsonserializes it.- Naming convention — new helper functions use
snake_case(build_default_*), consistent with the existing codebase style. - Test coverage — regression tests added for split config, compatibility reporting, and hold_molds behavior.
No new substantive issues found. The refactoring is clean and well-tested.
LHT129
left a comment
There was a problem hiding this comment.
This push addresses all previously raised review comments:
-
STORE_RAW_VECTOR path — Now correctly writes to
base_codes.quantization_paramsandprecise_codes.quantization_paramsviaApplyHoldMoldsToQuantizer, with a regression test added inbruteforce_parameter_test.cpp. -
size_t → uint64_t — The
CollectCompatibilityIssuesloop inparameter.cppnow usesuint64_tfor array traversal, consistent with project conventions. -
Test ordering dependency — The
CompatibilityReporttest infp32_quantizer_parameter_test.cppnow usesstd::mapfor order-independent assertions, and also covers JSON pointer escaping (/a~1b). -
Chain whitespace —
SplitStringalready trims whitespace around tokens, so"mrle, rabitq"and"mrle,rabitq"are both handled correctly. A dedicated test intransform_quantizer_parameter_test.cppverifies this.
No new issues found. The refactoring is clean, well-tested, and all prior concerns are resolved.
a11cf1c to
e96cbbe
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 32 out of 32 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
src/datacell/bucket_datacell_parameter_test.cpp:55
- This line exceeds the 100-character limit for C++ sources (see AGENTS.md hard constraints). Please wrap the statement to keep lines <= 100 chars.
auto parameter = MultiVectorDataCellParameter::CreateDefault(IO_TYPE_VALUE_BLOCK_MEMORY_IO);
LHT129
left a comment
There was a problem hiding this comment.
Review Summary
This is a large, well-executed refactoring that replaces template-based JSON parameter mapping (ConstParamMap + string templates) with explicit build_default_* functions and if-else chains across all index types. The PR also introduces CompatibilityReport/CompatibilityIssue for structured compatibility checking and RaBitQSplitConfig parsing/application.
What's been addressed from prior reviews
- STORE_RAW_VECTOR now correctly maps to
base_codes.quantization_params.hold_moldsandprecise_codes.quantization_params.hold_moldsviaApplyHoldMoldsToQuantizer parameter.hincludes<string>forCompatibilityIssue/CompatibilityReportsindi.cppincludes<unordered_set>- Test ordering issue fixed by using
std::mapfor order-independent assertions FlattenDataCellParameter::CreateDefaultnow handleshold_moldsfor both FP32 and INT8SplitStringtrims whitespace, so chain parsing handles spaces correctly- Unused
build_default_quantization_paramand stale includes removed
Items flagged in this review
- Error message format (
inner_index_parameter.cpp:105): The error message showstq_chain="mrle, rabitq"(with space), butSplitStringnormalizes whitespace, so the no-space variant is also valid. Consider using the canonical normalized form in the error message. - Performance note (
parameter.cpp:29):CollectCompatibilityIssuesdoes a full JSON round-trip for comparison. Fine for a non-hot-path compatibility check, but worth noting. - Consistency note (
hgraph_param_mapping.cpp:295): SINDI/SINDI v2 use asupported_keyswhitelist for validation; the other index types use if-else chains with a catch-all throw. Both approaches are functionally correct, but consistency would improve maintainability.
Overall assessment
The refactoring is sound. The explicit mapping approach is more readable and maintainable than the template-based system it replaces. All previously flagged issues have been addressed. No correctness or security issues found.
e96cbbe to
b3407eb
Compare
b3407eb to
84267a7
Compare
LHT129
left a comment
There was a problem hiding this comment.
[note] In src/parameter.cpp:92, common_size is derived from nlohmann::json::size() which returns size_t, but the loop iterates with uint64_t i. This can produce a signed/unsigned mismatch warning on platforms where size_t is narrower than uint64_t (e.g., 32-bit) or trigger -Wsign-compare warnings.
Consider using size_t for the loop index to match the container size type, or casting common_size explicitly to uint64_t if the project convention requires uint64_t everywhere:
for (size_t i = 0; i < common_size; ++i) {Build index parameter trees structurally and apply flat fields explicitly across HGraph, Pyramid, IVF, BruteForce, WARP, SIMQ, and SINDI. Centralize RaBitQ split parsing, remove the downstream split mutation, and add compatibility issue collection. Signed-off-by: LHT129 <tianlan.lht@antgroup.com> Assisted-by: Codex:GPT-5
| IOParamPtr | ||
| IOParameter::CreateDefault(const std::string& type_name) { | ||
| JsonType json; | ||
| json[TYPE_KEY].SetString(type_name); | ||
| json[IO_FILE_PATH_KEY].SetString(DEFAULT_FILE_PATH_VALUE); | ||
| return GetIOParameterByJson(json); | ||
| } |
| } | ||
| })"; | ||
| JsonType | ||
| build_default_simq_param(const JsonType& external_param) { |
There was a problem hiding this comment.
[note] build_default_simq_param defaults to IO_TYPE_VALUE_ASYNC_IO while all other builders (HGraph, BruteForce, Pyramid, IVF) default to IO_TYPE_VALUE_BLOCK_MEMORY_IO. This is consistent with the old SIMQ_PARAMS_TEMPLATE which also used IO_TYPE_VALUE_ASYNC_IO, so it is not a regression. Worth confirming whether this asymmetry is intentional (SIMQ is streaming-oriented) or should be unified for consistency.
LHT129
left a comment
There was a problem hiding this comment.
Review Summary
This PR replaces template-based JSON parameter mapping with structural builders and explicit flat-field translation across all index types. The refactoring is thorough and well-structured, with several improvements over the previous approach.
Issues Fixed from Previous Reviews
STORE_RAW_VECTORnow correctly appliesApplyHoldMoldsToQuantizerto both base and precise codes in BruteForce, HGraph, and Pyramidsize_treplaced withuint64_tinCollectCompatibilityIssues- Test ordering dependency fixed with
std::map SplitStringwhitespace trimming already handled
New Additions
CompatibilityReport/CompatibilityIssuewith JSON pointer path escaping (~0for~,~1for/) provides structured compatibility checkingRaBitQSplitConfigparsing andApplyRaBitQSplitConfigcentralize split quantizer configurationApplyHoldMoldsToQuantizerpropagates hold_molds flag through TQ chains to supported quantizers (fp32, int8)ValidateMRLEDimandRequiresRawVectorFor*helpers consolidate validation logicCreateDefaultfactory methods on parameter classes improve testability and code reuse- Whitelist-based validation for SINDI/SINDIV2 parameters provides clear error messages
One Minor Note
build_default_simq_paramdefaults toIO_TYPE_VALUE_ASYNC_IOwhile all other builders default toIO_TYPE_VALUE_BLOCK_MEMORY_IO. This is consistent with the old template, but worth confirming the asymmetry is intentional.
Overall
The refactoring eliminates the fragile string-template mutation chain and replaces it with structured, type-safe builders. The explicit if-else chains are verbose but clear and maintainable. No blocking issues found.
Summary
Replace template-based parameter mapping with structured defaults and explicit flat-field translation across index entry points.
Changes
Behavior change
codes_type=rabitq_splitnow requires the nested RaBitQ quantizer to already userabitq_version=split. Inconsistent configurations are rejected instead of silently mutatingrabitq_versionduringFlattenDataCellParameter::FromJson. Public index mappings construct the canonical split configuration before parsing.Testing
Related to #2729